﻿//
// Copyright 2011 Fredrik Mörk / www.alcedo.com
//
// This software is licensed under the Creative Commons
// Attribution-NonCommercial-ShareAlike 3.0 Unported license.
//
// For more info: http://creativecommons.org/licenses/by-nc-sa/3.0/
//
using System;
using System.Diagnostics;
using System.Linq;

namespace Alcedo.Diagnostics
{
	/// <summary>
	/// Contains functions for benchmarking code.
	/// </summary>
	public static class ActionDiagnosticExtensions
	{
		/// <summary>
		/// Measures it the time it takes to perform the given action.
		/// </summary>
		/// <param name="action">The action to perform.</param>
		/// <returns>The time it takes to peform the action once, in Ticks.</returns>
		/// <summary>The result is the average number of ticks taken to perform the given action. The first iteration
		/// is not included in the result in order to exclude time consumed by the JIT-compiler.</summary>
		public static double GetTimeTaken(this Action action)
		{
			return GetTimeTaken(action, 100);
		}

		/// <summary>
		/// Measures it the time it takes to perform the given action.
		/// </summary>
		/// <param name="action">The action to perform.</param>
		/// <param name="iterations">The number of iterations to perform. This must be greater than or equal to 2.</param>
		/// <returns>
		/// The time it takes to peform the action once, in Ticks.
		/// </returns>
		/// <summary>The result is the average number of ticks taken to perform the given action. The first iteration
		/// is not included in the result in order to exclude time consumed by the JIT-compiler.</summary>
		public static double GetTimeTaken(this Action action, int iterations)
		{
			if (action == null)
			{
				throw new ArgumentNullException("action");
			}
			if (iterations < 1)
			{
				throw new ArgumentException("The value of the iterations parameter must be greater to or equal to 1.", "iterations");
			}

			return Enumerable.Range(1, iterations + 1).Select(n =>
			{
				var sw = new Stopwatch();
				sw.Start();
				action();
				return sw.ElapsedMilliseconds;
			})
					.Skip(1) // skip the first iteration to avoid including JIT compilation in the result
					.Average();
		}
		
		/// <summary>
		/// Measures it the time it takes to perform the given action.
		/// </summary>
		/// <param name="action">The action to perform.</param>
		/// <param name="resultMessageTemplate">The result message template.</param>
		/// <returns>
		/// A string based on <paramref name="resultMessageTemplate" /> where the first
		/// placeholder will contain the result.
		/// </returns>
		/// <summary>The result is the average number of ticks taken to perform the given action. The first iteration
		/// is not included in the result in order to exclude time consumed by the JIT-compiler.</summary>
		public static string GetTimeTaken(this Action action, string resultMessageTemplate)
		{
			return GetTimeTaken(action, resultMessageTemplate, 100);
		}

		/// <summary>
		/// Measures it the time it takes to perform the given action.
		/// </summary>
		/// <param name="action">The action to perform.</param>
		/// <param name="resultMessageTemplate">The result message template.</param>
		/// <param name="iterations">The number of iterations to perform.</param>
		/// <returns>
		/// A string based on <paramref name="resultMessageTemplate"/> where the first
		/// placeholder will contain the result.
		/// </returns>
		/// <summary>The result is the average number of ticks taken to perform the given action. The first iteration
		/// is not included in the result in order to exclude time consumed by the JIT-compiler.</summary>
		public static string GetTimeTaken(this Action action, string resultMessageTemplate, int iterations)
		{
			return string.Format(resultMessageTemplate, GetTimeTaken(action, iterations));
		}
	}
}